package com.ifast.api.listener;

import com.baomidou.mybatisplus.toolkit.CollectionUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.realm.Realm;

import java.util.Collection;

public class ModularRealmAuthenticator extends AbstractAuthenticator {

    //Realm集合，用于用户登陆验证时候
    private Collection<Realm> realms;

    //配置多个Realm使用策略
    private AuthenticationStrategy authenticationStrategy;

    /*--------------------------------------------
    |         C O N S T R U C T O R S           |
    ============================================*/



    //设置Realms，提供PAM配置支持
    public void setRealms(Collection<Realm> realms) {
        this.realms = realms;
    }


    protected Collection<Realm> getRealms() {
        return this.realms;
    }



    public AuthenticationStrategy getAuthenticationStrategy() {
        if (this.authenticationStrategy==null){
            return  new AtLeastOneSuccessfulStrategy();
        }
        return authenticationStrategy;
    }

    //覆盖默认多Realm使用策略
    public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
        this.authenticationStrategy = authenticationStrategy;
    }

    //确保Realms不为空，至少需要一个，否则抛异常。
    protected void assertRealmsConfigured() throws IllegalStateException {
        Collection<Realm> realms = getRealms();
        if (CollectionUtils.isEmpty(realms)) {
            String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
                    "present to execute an authentication attempt.";
            throw new IllegalStateException(msg);
        }
    }


    //执行多个Realm验证逻辑
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        //获取多个Realm使用策略
        AuthenticationStrategy strategy = getAuthenticationStrategy();

        /**
         以下的AuthenticationStrategy的方法我只针对AtLeastOneSuccessfulStrategy来说!!!

         **/

        //执行所有Realm之前，AuthenticationStrategy首先初始化个SimpleAuthenticationInfo
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);


        for (Realm realm : realms) {
            //执行单个Realm之前，直接返回aggregate
            aggregate = strategy.beforeAttempt(realm, token, aggregate);
            //Realm先判断是否支持该token
            if (realm.supports(token)) {

                //log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                Throwable t = null;
                try {
                    //realm调用真正的验证登陆处理逻辑
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
                    t = throwable;
                }
                //单个Realm执行之后，合并info和aggregate
                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
                //log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }
        //所有Realm执行之后，判断是否aggregate的getPrincipals是否为空，因为至少需要一个验证成功，所以为空则抛出异常
        aggregate = strategy.afterAllAttempts(token, aggregate);
        //执行到这里，至少有一个Realm登陆验证成功。
        return aggregate;
    }
    //单个Realm执行的验证逻辑
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        //首先判断Realm是否支持该token
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" +
                    token + "].  Please ensure that the appropriate Realm implementation is " +
                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        //realm调用getAuthenticationInfo(token)来执行真正的数据验证交互。该方法如果验证失败会抛出相对应异常
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            String msg = "Realm [" + realm + "] was unable to find account data for the " +
                    "submitted AuthenticationToken [" + token + "].";
            throw new UnknownAccountException(msg);
        }
        return info;
    }

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        //首先验证realm是否为空
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            //单个realm则执行单realm逻辑处理
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            //处理多realm逻辑处理（依据AuthenticationStrategy）
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
}
